home *** CD-ROM | disk | FTP | other *** search
- unit BldSQL;
-
- interface
-
- uses
- Classes;
-
- procedure BuildSQL1(aQuery, aIndexTable: string; aSQL: TStrings);
- { Allow single word search values and operators AND, OR, NOT, NEAR:
- books
- books AND author
- books OR magazines
- books NOT fiction
- book NEAR reference
- }
-
- procedure BuildSQL2(aQuery, aIndexTable: string; aSQL: TStrings);
- { Allow only a single multi-word search phrase delimited by quotes:
- "edibility is good"
- }
-
-
- implementation
-
- uses
- SysUtils;
-
- const
- NearRange = 3;
-
- type
- TQueryOperator = (opNONE, opAND, opOR, opNOT, opNEAR);
-
- procedure MakeWordList(aText: string; aList: TStrings);
- var
- P, T: PChar;
- Word: string;
- begin
- aList.Clear;
- aText := aText + #32;
- P := PChar(aText);
- T := PChar(aText);
- repeat
- Inc(P);
- if P^ = #32 then begin
- SetString(Word, T, P - T);
- T := P + 1;
- aList.Add(Word);
- end;
- until P^ = #0;
- end;
-
- procedure ParseQuery(aQuery: AnsiString;
- var aOperand1,
- aOperand2: AnsiString;
- var aOperator: TQueryOperator);
- type
- TParseState = (psBegin, psExpectOperand, psExpectOperator);
- const
- WhiteSpace = [#32, #8, #9, #13, #10];
- var
- P: PChar;
- State: TParseState;
- InWhiteSpace: Boolean;
- OperandIndex: Integer;
- TempStr: AnsiString;
- begin
- aOperand1 := '';
- aOperand2 := '';
- aOperator := opNONE;
- State := psBegin;
- InWhiteSpace := False;
- OperandIndex := 1;
- P := PChar(aQuery);
- try
- while (P^ <> #0) do begin
- { Eat leading whitespace }
- while P^ in WhiteSpace do begin
- InWhiteSpace := True;
- Inc(P);
- if P^ = #0 then Exit;
- end;
-
- { Change the parsing state upon encountering whitespace }
- if InWhiteSpace or (State = psBegin) then begin
- case State of
- psBegin:
- begin
- State := psExpectOperand;
- OperandIndex := 1;
- end;
- psExpectOperand:
- State := psExpectOperator;
- psExpectOperator:
- begin
- State := psExpectOperand;
- OperandIndex := 2;
- end;
- end;
- TempStr := '';
- InWhiteSpace := False;
- end;
-
- case State of
- psExpectOperand:
- begin
- if P^ = '"' then begin { we are in a literal }
- Inc(P);
- while P^ <> '"' do begin
- if P^ = #0 then
- raise Exception.Create('Literal not terminated');
-
- if P^ in WhiteSpace then P^ := #32;
- if not ((P^ = #32) and ((P - 1)^ = #32)) then
- TempStr := TempStr + P^;
- Inc(P);
- end;
- Inc(P);
- end
- else begin
- while not (P^ in WhiteSpace) do begin
- if P^ = #0 then Break;
- TempStr := TempStr + P^;
- Inc(P);
- end;
- end;
-
- case OperandIndex of
- 1: aOperand1 := TempStr;
- 2: aOperand2 := TempStr;
- end;
- end;
- psExpectOperator:
- begin
- while not (P^ in WhiteSpace) do begin
- if P^ = #0 then Break;
- TempStr := TempStr + UpCase(P^);
- Inc(P);
- end;
- if TempStr = 'AND' then
- aOperator := opAND
- else if TempStr = 'OR' then
- aOperator := opOR
- else if TempStr = 'NOT' then
- aOperator := opNOT
- else if TempStr = 'NEAR' then
- aOperator := opNEAR
- else
- raise Exception.CreateFmt('Unknown Query Operator "%s"', [TempStr]);
- end;
- end;
- end;
- finally
- aOperand1 := Uppercase(aOperand1);
- aOperand2 := Uppercase(aOperand2);
- end;
- end;
-
- procedure BuildSQL1(aQuery, aIndexTable: AnsiString; aSQL: TStrings);
- var
- Operand1, Operand2: AnsiString;
- Operator: TQueryOperator;
- NotWord: string;
- begin
- ParseQuery(aQuery, Operand1, Operand2, Operator);
- if (Pos(' ', Operand1) <> 0) or (Pos(' ', Operand2) <> 0) then
- raise Exception.Create('Multi-word phrases not allowed here');
-
- with aSQL do begin
- Clear;
-
- { This part is specific to the data table }
- Add('SELECT Biolife."Species No", Common_Name, Biolife."Species Name" FROM Biolife');
- Add('WHERE Biolife."Species No" IN ');
-
- case Operator of
- opNONE:
- begin
- Add(Format('(SELECT DISTINCT RecordID FROM %s', [aIndexTable]));
- Add(Format('WHERE Keyword = "%s")', [Operand1]));
- end;
- opOR:
- begin
- Add(Format('(SELECT DISTINCT RecordID FROM %s', [aIndexTable]));
- Add(Format('WHERE Keyword = "%s"', [Operand1]));
- Add(Format('OR Keyword = "%s")', [Operand2]));
- end;
- opAND, opNOT:
- begin
- if Operator = opNOT then NotWord := 'NOT' else NotWord := '';
- Add(Format('(SELECT DISTINCT RecordID FROM %s A', [aIndexTable]));
- Add(Format(' WHERE Keyword = "%s" AND %s EXISTS', [Operand1, NotWord]));
- Add(Format(' (SELECT RecordID FROM %s B', [aIndexTable]));
- Add( ' WHERE A.RecordID = B.RecordID AND');
- Add(Format(' B.Keyword = "%s"))', [Operand2]));
- end;
- opNEAR:
- begin
- if CompareText(aIndexTable, 'BIOLIFEIDX2') <> 0 then
- raise Exception.Create('BiolifeIdx2 Required for NEAR');
-
- Add(Format('(SELECT DISTINCT RecordID FROM %s A', [aIndexTable]));
- Add(Format(' WHERE Keyword = "%s" AND %s EXISTS', [Operand1, NotWord]));
- Add(Format(' (SELECT RecordID FROM %s B', [aIndexTable]));
- Add( ' WHERE A.RecordID = B.RecordID AND');
- Add(Format(' B.Keyword = "%s" AND', [Operand2]));
- Add(Format(' A.WordOffset - B.WordOffset <= %d AND', [NearRange]));
- Add(Format(' A.WordOffset - B.WordOffset >= %d))', [-NearRange]));
- end;
- end;
- end;
- end;
-
- procedure BuildSQL2(aQuery, aIndexTable: AnsiString; aSQL: TStrings);
- var
- Operand1, Operand2: AnsiString;
- Operator: TQueryOperator;
- PhraseWords: TStringList;
- Temp: string;
- I: Integer;
- begin
- ParseQuery(aQuery, Operand1, Operand2, Operator);
- if Operator <> opNONE then
- raise Exception.Create('Operators not allowed here');
-
- with aSQL do begin
- Clear;
-
- { This part is specific to the data table }
- Add('SELECT Biolife."Species No", Common_Name, Biolife."Species Name" FROM Biolife');
- Add('WHERE Biolife."Species No" IN ');
-
- { Check for a single word value }
- if Pos(' ', Operand1) = 0 then begin
- Add(Format('(SELECT DISTINCT RecordID FROM %s', [aIndexTable]));
- Add(Format(' WHERE Keyword = "%s")', [Operand1]));
- end
- else begin
- PhraseWords := TStringList.Create;
- try
- MakeWordList(Operand1, PhraseWords);
-
- Add(Format('(SELECT RecordID FROM %s A', [aIndexTable]));
- Add(Format(' WHERE A.Keyword = "%s" AND EXISTS(', [PhraseWords[0]]));
- Add(Format(' SELECT RecordID FROM %s B', [aIndexTable]));
- Add( ' WHERE A.RecordID = B.RECORDID AND');
- for I := 1 to PhraseWords.Count - 1 do begin
- Temp := Format('(B.Keyword = "%s" AND B.WordOffset = A.WordOffset + %d)', [PhraseWords[I], I]);
- if I = 1 then Temp := '(' + Temp;
- if I < PhraseWords.Count - 1 then Temp := Temp + ' OR'
- else Temp := Temp + ')';
- Add(Temp);
- end;
- Add( ' GROUP BY RecordID');
- Add(Format(' HAVING COUNT(*) = %d))', [PhraseWords.Count - 1]));
- finally
- PhraseWords.Free;
- end;
- end;
- end;
- end;
-
- end.
-